;-----------------------------------------------------------------------;
; CLOCK.ASM								;
;									;
; Pictor, Version 1.51, Copyright (c) 1992-94 SoftCircuits.		;
; Redistributed by permission.						;
;-----------------------------------------------------------------------;
%	.MODEL	memmodel,c

IFDEF __WASM__
	EXTRN watcom_c atexit:PROC ;Changed memmodel to watcom_c- W. Jones
ELSE
	EXTRN atexit:PROC
ENDIF

	.DATA
	EXTRN	_PL_segment:WORD,_PL_columns:WORD
	EXTRN	_PL_snowcheck:WORD

show_row	DW	0		;Screen coordinates for clock
show_column	DW	0

time_buffer	DB	"0 0 : 0 0 a m "
TIME_BUFFER_LEN	EQU	(($-time_buffer)/2)

installed_flag	DB	0		;1 = handler is installed
registered_flag	DB	0		;1 = removeclock registered w/atexit
counter		DW	0		;


	.CODE
old1C_handler	DD	0		;Save old handler in code segment

;-----------------------------------------------------------------------;
; Creates an ASCII time string based on the current time.		;
;									;
; Usage:	This procedure cannot be called directly from C.	;
;-----------------------------------------------------------------------;
IFDEF	??version			;Turbo Assembler
create_time	PROC	NEAR
ELSE
create_time	PROC NEAR PRIVATE
ENDIF
	mov	time_buffer[10],'a'	;Assume 'am' for now
	mov	ax,0040h
	mov	es,ax
	mov	ax,es:[006Eh]		;Read timer high count
	mov	bx,ax			;Preserve ax
	mov	ax,12			;Assume midnight
	cmp	bx,0			;Is it midnight?
	je	set_hours		;Yes
	mov	ax,bx			;Else restore ax
	cmp	ax,12			;Is it before noon?
	jb	set_hours		;Yes, 'am' okay
	mov	time_buffer[10],'p'	;Else must be 'pm'
	cmp	al,12			;Noon?
	je	set_hours		;Yes
	sub	al,12			;Else adjust 'pm' hours
set_hours:
	aam				;Convert al to binary coded decimal
	add	ax,3030h		;Convert ax to 2 ASCII digits
	cmp	ah,'0'			;Is leading digit '0'?
	jne	hours_ok		;No, hours are ok
	mov	ah,' '			;Else clear leading '0'
hours_ok:
	mov	time_buffer[0],ah	;Create hour portion of string
	mov	time_buffer[2],al
	mov	al,es:[006Dh]		;Read timer low count
	mov	dl,60
	mul	dl
	mov	al,ah
	aam				;Convert al to binary coded decimal
	add	ax,3030h		;Convert ax to 2 ASCII digits
	mov	time_buffer[6],ah	;Create minutes portion of string
	mov	time_buffer[8],al
	ret
create_time	ENDP

;-----------------------------------------------------------------------;
; Displays the time string to the screen.				;
;									;
; Usage:	This procedure cannot be called directly from C.	;
;-----------------------------------------------------------------------;
IFDEF	??version			;Turbo Assembler
show_time	PROC	NEAR
ELSE
show_time	PROC NEAR PRIVATE
ENDIF
	cld
	mov	si,OFFSET time_buffer	;ds:si = string, es:di = screen
	mov	es,_PL_segment
	mov	ax,show_row
	mul	BYTE PTR _PL_columns
	add	ax,show_column
	shl	ax,1
	mov	di,ax
	mov	cx,TIME_BUFFER_LEN
	cmp	_PL_snowcheck,0
	je	no_snow_check
	mov	dx,03DAh		;CGA status port address
snow_check:
	lodsw
	mov	bx,ax
scan_low:
	in	al,dx
	shr	al,1
	jc	scan_low
	cli
scan_high:
	in	al,dx
	shr	al,1
	jnc	scan_high
	xchg	ax,bx
	stosw
	sti
	loop	snow_check
	ret	
no_snow_check:
	rep	movsw
	ret
show_time	ENDP

;-----------------------------------------------------------------------;
; Timer interrupt handler. If the counter has expired, the time string	;
; is updated to reflect the current time. In either case, the time	;
; string is written to the screen and this procedure chains to the	;
; original timer handler.						;
;									;
; Usage:	This procedure cannot be called directly from C.	;
;-----------------------------------------------------------------------;
IFDEF	??version			;Turbo Assembler
int1C_handler	PROC	FAR
ELSE
int1C_handler	PROC FAR PRIVATE
ENDIF
	sti				;Interrupts on
	push	ax
	push	bx
	push	cx
	push	dx
	push	si
	push	di
	push	bp
	push	ds
	push	es
	mov	ax,@Data		;ds = data segment
	mov	ds,ax
	dec	counter			;Decrement counter
	jns	display_time		;Display only if no underflow
	call	create_time		;Else update time string
	mov	counter,500		;Reset timer
display_time:
	call	show_time		;Display time
	pop	es
	pop	ds
	pop	bp
	pop	di
	pop	si
	pop	dx
	pop	cx
	pop	bx
	pop	ax
	cli				;Handler expects interrupts off
	jmp	cs:old1C_handler	;Chain to original handler
int1C_handler	ENDP

	PUBLIC	removeclock
;-----------------------------------------------------------------------;
; Deactivates the clock by removing the clock interrupt handler. This	;
; function fails if the handler has not been installed, or if the	;
; interrupt vector has been modified since the handler was installed.	;
;									;
; Usage:	int removeclock(void);					;
; Returns:	0 = success, -1 = error					;
;-----------------------------------------------------------------------;

IFDEF __WASM__
	removeclock PROC watcom_c ;See pictor.h declaration for reasoning using watcall here- W. Jones. 
	;push ax
	push bx ;All registers non-volatile in __watcall (excepting those used for return values)- W. Jones.
	push dx
ELSE
	removeclock PROC c 
ENDIF
	mov	ax,-1			;Return error if we haven't
	cmp	installed_flag,0	; installed our handler
	je	end_removeclock
	mov	ax,351Ch		;Get interrupt 1Ch vector
	int	21h
	mov	ax,-1			;Return error if vector doesn't
	cmp	bx,OFFSET int1C_handler	; still point to our handler
	jne	end_removeclock
	mov	bx,es
	mov	ax,cs
	cmp	ax,bx
	jne	end_removeclock
	mov	ax,251Ch		;Restore original vector
	push	ds
	lds	dx,cs:old1C_handler
	int	21h
	pop	ds			;Restore ds
	mov	installed_flag,0	;Indicate we're no longer installed
	sub	ax,ax
end_removeclock:
IFDEF __WASM__
	pop dx
	pop bx ;All registers non-volatile in __watcall (excepting those used for return values)- W. Jones. 
	;pop ax ;atexit() will crash and burn without this after return- W. Jones.
ENDIF
	ret
removeclock	ENDP

	PUBLIC	installclock
;-----------------------------------------------------------------------;
; Installs an interrupt handler that will display the time at the	;
; specified location. It then registers removeclock with the ANSI C	;
; library function atexit().						;
;									;
; Usage:	int installclock(int row,int column,int color);		;
; Returns:	0 = success, -1 = error					;
;-----------------------------------------------------------------------;
installclock	PROC USES si, row:WORD,column:WORD,color:WORD
	mov	ax,-1			;Return error if were already
	cmp	installed_flag,1	; installed
	je	end_installclock
	mov	ax,row			;Adjust and save screen coordinates
	dec	ax
	mov	show_row,ax
	mov	ax,column
	dec	ax
	mov	show_column,ax
	mov	ax,color		;Set attribute bytes of buffer to
	mov	si,OFFSET time_buffer+1	; specified color
	mov	cx,TIME_BUFFER_LEN
color_loop:
	mov	[si],al
	add	si,2
	loop	color_loop
	mov	ax,351Ch		;Get old timer interrupt vector
	int	21h
	mov	WORD PTR cs:old1C_handler[0],bx
	mov	WORD PTR cs:old1C_handler[2],es
	push	ds			;Set new timer interrupt vector
	mov	ax,251Ch
	push	cs
	pop	ds
	mov	dx,OFFSET int1C_handler
	int	21h
	pop	ds
	mov	installed_flag,1	;Indicate we've been installed
	mov	ax,0
	cmp	registered_flag,1
	je	end_installclock
	mov	registered_flag,1
	
	IFDEF __WASM__ ;Compensate for 16-bit WATCOM calling convention- W. Jones
		IF	@CodeSize		;Register removeclock with atexit()
			mov dx, cs
			mov	ax, OFFSET removeclock
		ELSE
			mov	ax,OFFSET removeclock
		ENDIF
		call	atexit	
	ELSE
		IF	@CodeSize		;Register removeclock with atexit()
		push	cs
		ENDIF
		mov	ax,OFFSET removeclock
		push	ax
		call	atexit
		IF	@CodeSize
		add	sp,4
		ELSE
		inc	sp
		inc	sp
		ENDIF
	ENDIF
	
	cmp	ax,0			;Was atexit() successful?
	je	end_installclock	;Yes, return 0
	call	removeclock		;Else uninstall handler
	mov	registered_flag,0
	mov	ax,-1			;Return error
end_installclock:
	ret
installclock	ENDP

	END
